package com.nulldev.util.internal.backport.httpclient_rw.backports.cleaner;

import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.function.Function;

import com.nulldev.util.JVM.reflections.Reflections;

/**
 * {@code Cleaner} manages a set of object references and corresponding cleaning
 * actions.
 * <p>
 * Cleaning actions are {@link #register(Object object, Runnable action)
 * registered} to run after the cleaner is notified that the object has become
 * phantom reachable. The cleaner uses {@link PhantomReference} and
 * {@link ReferenceQueue} to be notified when the
 * <a href="package-summary.html#reachability">reachability</a> changes.
 * <p>
 * Each cleaner operates independently, managing the pending cleaning actions
 * and handling threading and termination when the cleaner is no longer in use.
 * Registering an object reference and corresponding cleaning action returns a
 * {@link Cleanable Cleanable}. The most efficient use is to explicitly invoke
 * the {@link Cleanable#clean clean} method when the object is closed or no
 * longer needed. The cleaning action is a {@link Runnable} to be invoked at
 * most once when the object has become phantom reachable unless it has already
 * been explicitly cleaned. Note that the cleaning action must not refer to the
 * object being registered. If so, the object will not become phantom reachable
 * and the cleaning action will not be invoked automatically.
 * <p>
 * The execution of the cleaning action is performed by a thread associated with
 * the cleaner. All exceptions thrown by the cleaning action are ignored. The
 * cleaner and other cleaning actions are not affected by exceptions in a
 * cleaning action. The thread runs until all registered cleaning actions have
 * completed and the cleaner itself is reclaimed by the garbage collector.
 * <p>
 * The behavior of cleaners during {@link System#exit(int) System.exit} is
 * implementation specific. No guarantees are made relating to whether cleaning
 * actions are invoked or not.
 * <p>
 * Unless otherwise noted, passing a {@code null} argument to a constructor or
 * method in this class will cause a {@link java.lang.NullPointerException
 * NullPointerException} to be thrown.
 *
 * @apiNote The cleaning action is invoked only after the associated object
 *          becomes phantom reachable, so it is important that the object
 *          implementing the cleaning action does not hold references to the
 *          object. In this example, a static class encapsulates the cleaning
 *          state and action. An "inner" class, anonymous or not, must not be
 *          used because it implicitly contains a reference to the outer
 *          instance, preventing it from becoming phantom reachable. The choice
 *          of a new cleaner or sharing an existing cleaner is determined by the
 *          use case.
 *          <p>
 *          If the CleaningExample is used in a try-finally block then the
 *          {@code close} method calls the cleaning action. If the {@code close}
 *          method is not called, the cleaning action is called by the Cleaner
 *          when the CleaningExample instance has become phantom reachable.
 * 
 *          <pre>{@code
 * public class CleaningExample implements AutoCloseable {
 *        // A cleaner, preferably one shared within a library
 *        private static final Cleaner cleaner = <cleaner>;
 *
 *        static class State implements Runnable {
 *
 *            State(...) {
 *                // initialize State needed for cleaning action
 *            }
 *
 *            public void run() {
 *                // cleanup action accessing State, executed at most once
 *            }
 *        }
 *
 *        private final State;
 *        private final Cleaner.Cleanable cleanable
 *
 *        public CleaningExample() {
 *            this.state = new State(...);
 *            this.cleanable = cleaner.register(this, state);
 *        }
 *
 *        public void close() {
 *            cleanable.clean();
 *        }
 *    }
 * }</pre>
 * 
 *          The cleaning action could be a lambda but all too easily will
 *          capture the object reference, by referring to fields of the object
 *          being cleaned, preventing the object from becoming phantom
 *          reachable. Using a static nested class, as above, will avoid
 *          accidentally retaining the object reference.
 *          <p>
 *          <a id="compatible-cleaners"></a> Cleaning actions should be prepared
 *          to be invoked concurrently with other cleaning actions. Typically
 *          the cleaning actions should be very quick to execute and not block.
 *          If the cleaning action blocks, it may delay processing other
 *          cleaning actions registered to the same cleaner. All cleaning
 *          actions registered to a cleaner should be mutually compatible.
 * @since 9
 */
public final class Cleaner {

	/**
	 * The Cleaner implementation.
	 */
	final Reflections impl; /* CleanerImpl */

	static {
		if (Reflections.is("jdk.internal.ref.CleanerImpl")) {
			new Reflections("jdk.internal.ref.CleanerImpl", false).invoke("setCleanerImplAccess", new Function<Cleaner, Object>() {
				@Override
				public Object apply(Cleaner cleaner) {
					return cleaner.impl;
				}
			});
		}
	}

	/**
	 * Construct a Cleaner implementation and start it.
	 */
	private Cleaner() {
		impl = Reflections.is("jdk.internal.ref.CleanerImpl") ? new Reflections("jdk.internal.ref.CleanerImpl", true) : null;
	}

	/**
	 * Returns a new {@code Cleaner}.
	 * <p>
	 * The cleaner creates a {@link Thread#setDaemon(boolean) daemon thread} to
	 * process the phantom reachable objects and to invoke cleaning actions. The
	 * {@linkplain java.lang.Thread#getContextClassLoader context class loader} of
	 * the thread is set to the {@link ClassLoader#getSystemClassLoader() system
	 * class loader}. The thread has no permissions, enforced only if a
	 * {@link java.lang.System#setSecurityManager(SecurityManager) SecurityManager
	 * is set}.
	 * <p>
	 * The cleaner terminates when it is phantom reachable and all of the registered
	 * cleaning actions are complete.
	 *
	 * @return a new {@code Cleaner}
	 *
	 * @throws SecurityException if the current thread is not allowed to create or
	 *                           start the thread.
	 */
	public static Cleaner create() {
		Cleaner cleaner = new Cleaner();
		Reflections.encapsulate(cleaner.impl).invoke("start", cleaner, null);
		return cleaner;
	}

	/**
	 * Returns a new {@code Cleaner} using a {@code Thread} from the
	 * {@code ThreadFactory}.
	 * <p>
	 * A thread from the thread factory's {@link ThreadFactory#newThread(Runnable)
	 * newThread} method is set to be a {@link Thread#setDaemon(boolean) daemon
	 * thread} and started to process phantom reachable objects and invoke cleaning
	 * actions. On each call the {@link ThreadFactory#newThread(Runnable) thread
	 * factory} must provide a Thread that is suitable for performing the cleaning
	 * actions.
	 * <p>
	 * The cleaner terminates when it is phantom reachable and all of the registered
	 * cleaning actions are complete.
	 *
	 * @param threadFactory a {@code ThreadFactory} to return a new {@code Thread}
	 *                      to process cleaning actions
	 * @return a new {@code Cleaner}
	 *
	 * @throws IllegalThreadStateException if the thread from the thread factory was
	 *                                     {@link Thread.State#NEW not a new
	 *                                     thread}.
	 * @throws SecurityException           if the current thread is not allowed to
	 *                                     create or start the thread.
	 */
	public static Cleaner create(ThreadFactory threadFactory) {
		Objects.requireNonNull(threadFactory, "threadFactory");
		Cleaner cleaner = new Cleaner();
		Reflections.encapsulate(cleaner.impl).invoke("start", cleaner, threadFactory);
		return cleaner;
	}

	/**
	 * Registers an object and a cleaning action to run when the object becomes
	 * phantom reachable. Refer to the <a href="#compatible-cleaners">API Note</a>
	 * above for cautions about the behavior of cleaning actions.
	 *
	 * @param obj    the object to monitor
	 * @param action a {@code Runnable} to invoke when the object becomes phantom
	 *               reachable
	 * @return a {@code Cleanable} instance
	 */
	public Cleanable register(Object obj, Runnable action) {
		Objects.requireNonNull(obj, "obj");
		Objects.requireNonNull(action, "action");
		try {
			return (Cleanable) Reflections.reflect("jdk.internal.ref.CleanerImpl.PhantomCleanableRef", obj, this, action).object();
		} catch (Exception e) {
			return FALLBACK;
		}
	}

	private static final Cleanable FALLBACK = new Cleanable() {

		@Override
		public void clean() {
		}
	};

	/**
	 * {@code Cleanable} represents an object and a cleaning action registered in a
	 * {@code Cleaner}.
	 * 
	 * @since 9
	 */
	public interface Cleanable {
		/**
		 * Unregisters the cleanable and invokes the cleaning action. The cleanable's
		 * cleaning action is invoked at most once regardless of the number of calls to
		 * {@code clean}.
		 */
		void clean();
	}

}